namespace AgentJohnson
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Windows.Forms;
  using JetBrains.Annotations;
  using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
  using Resharper.Abstractions.Actions;
  using Resharper.Abstractions.CodeDom.Expressions;
  using Resharper.Abstractions.CodeDom.Expressions.InvocationExpressions;
  using Resharper.Abstractions.CodeDom.Statements;
  using Resharper.Abstractions.CodeDom.Statements.TryStatements;
  using Resharper.Abstractions.Extensions.DocumentationExtensions;

  /// <summary>Defines the <see cref="CatchExceptions"/> class.</summary>
  [ContextAction("Catch Exceptions [Agent Johnson]", "Catches exceptions.")]
  public class CatchExceptions : ContextActionBase
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="CatchExceptions"/> class.</summary>
    /// <param name="provider">The provider.</param>
    public CatchExceptions([NotNull] ICSharpContextActionDataProvider provider) : base(provider)
    {
    }

    #endregion

    #region Methods

    /// <summary>Determines whether this instance can execute the specified action context.</summary>
    /// <param name="context">The action context.</param>
    /// <returns><c>true</c> if this instance can execute the specified action context; otherwise, <c>false</c>.</returns>
    protected override bool CanExecute(CanExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      return this.GetModel(context) != null;
    }

    /// <summary>Executes the action.</summary>
    /// <param name="context">The execute context.</param>
    protected override void Execute(ExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      var model = this.GetModel(context);
      if (model == null)
      {
        return;
      }

      var code = "try\r\n{\r\n " + model.Statement + "\r\n}\r\n";
      foreach (var exception in model.Exceptions)
      {
        code += "catch (" + exception.ExceptionTypeName + ")\r\n{\r\n}";
      }

      var tryStatement = context.Factory.CreateStatement(code) as TryStatement;
      if (tryStatement == null)
      {
        MessageBox.Show("Failed to create code.", "Agent Johnson");
        return;
      }

      var result = model.Statement.ReplaceBy(tryStatement);
      if (result == null)
      {
        return;
      }

      context.Formatter.Format(result);
    }

    /// <summary>Gets the text.</summary>
    /// <returns>Returns the text.</returns>
    protected override string GetText()
    {
      return "Catch exceptions [Agent Johnson]";
    }

    /// <summary>Examines the catches.</summary>
    /// <param name="tryStatement">The try statement.</param>
    /// <param name="exceptions">The exceptions.</param>
    private static void ExamineCatches([NotNull] TryStatement tryStatement, [NotNull] List<DocumentedException> exceptions)
    {
      if (tryStatement == null)
      {
        throw new ArgumentNullException("tryStatement");
      }

      if (exceptions == null)
      {
        throw new ArgumentNullException("exceptions");
      }

      var catches = tryStatement.Catches.Select(clause => clause.ExceptionTypeName).Where(exceptionType => !string.IsNullOrEmpty(exceptionType)).ToList();

      for (var n = exceptions.Count() - 1; n >= 0; n--)
      {
        var type = exceptions.ElementAt(n);

        if (catches.Contains(type.ExceptionTypeName))
        {
          exceptions.RemoveAt(n);
        }
      }
    }

    /// <summary>Gets the model.</summary>
    /// <param name="context">The context.</param>
    /// <returns>Returns the model.</returns>
    private Model GetModel(ProviderContext context)
    {
      var invocationExpression = context.GetSelectedInvocationExpression();
      if (invocationExpression == null)
      {
        return null;
      }

      var statement = invocationExpression.GetContainingStatement();
      if (statement == null)
      {
        return null;
      }

      var declaredElement = invocationExpression.InvokedElement;
      if (declaredElement == null)
      {
        return null;
      }

      var exceptions = declaredElement.DocumentedExceptions().ToList();
      var tryStatement = context.GetSelectedTryStatement();

      while (tryStatement != null)
      {
        ExamineCatches(tryStatement, exceptions);

        if (exceptions.Count == 0)
        {
          return null;
        }

        tryStatement = tryStatement.GetContainingTryStatement();
      }

      return new Model
      {
        Statement = statement,
        Exceptions = exceptions
      };
    }

    #endregion

    /// <summary>Defines the <see cref="Model"/> class.</summary>
    private class Model
    {
      #region Properties

      /// <summary>
      /// Gets or sets the exceptions.
      /// </summary>
      /// <value>The exceptions.</value>
      [NotNull]
      public List<DocumentedException> Exceptions { get; set; }

      /// <summary>
      /// Gets or sets the statement.
      /// </summary>
      /// <value>The statement.</value>
      [NotNull]
      public Statement Statement { get; set; }

      #endregion
    }
  }
}